package org.hibernate.tool.ipan.reveng;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.hibernate.cfg.Environment;
import org.hibernate.cfg.JDBCMetaDataConfiguration;
import org.hibernate.cfg.reveng.JDBCToHibernateTypeHelper;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.ForeignKey;
import org.hibernate.mapping.PrimaryKey;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



/**
 * 数据库反转工具类
 * 
 * @author iPan
 * @version 2013-12-11
 */
public class DatabaseReverser implements IDatabaseReverser {

	private List<Table> allTableList = new ArrayList<Table>();
	// Key为小写字母
	private Map<String, Table> tableMap = new TreeMap<String, Table>();
	// Key为小写字母
	private Map<String, Table> viewMap = new TreeMap<String, Table>();
	private JDBCMetaDataConfiguration cfg = null;
	private String catalog = null;
	private String schema = null;
	private boolean flushed = false;
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	private DatabaseReverser(JDBCMetaDataConfiguration cfg) {
		this.cfg = cfg;
		this.catalog = cfg.getProperty(Environment.DEFAULT_CATALOG);
		this.schema = cfg.getProperty(Environment.DEFAULT_SCHEMA);
	}

	public static synchronized DatabaseReverser createDatabaseReverser(JDBCMetaDataConfiguration cfg, boolean lazyinit) {
		if (cfg == null) {
			throw new IllegalArgumentException("JDBCMetaDataConfiguration不能为空！");
		}
		DatabaseReverser _INSTANCE = new DatabaseReverser(cfg);
		if (!lazyinit) {
			_INSTANCE.flush();
		}
		return _INSTANCE;
	}

	public synchronized void flush() {
		clear();
		cfg.readFromJDBC(catalog, schema); // 重新读取
		this.tableMap.putAll(cfg.getTableMap());
		this.viewMap.putAll(cfg.getViewMap());
		allTableList.addAll(this.tableMap.values());
		allTableList.addAll(this.viewMap.values());
		this.flushed = true;
	}

	public List<Table> getViewList() {
		checkAndFlush();
		return (viewMap.values() != null) ? new ArrayList<Table>(viewMap.values()) : null;
	}

	public List<Table> getTableList() {
		checkAndFlush();
		return (tableMap.values() != null) ? new ArrayList<Table>(tableMap.values()) : null;
	}

	public List<String> getViewNameList() {
		checkAndFlush();
		return (viewMap.keySet() != null) ? new ArrayList<String>(viewMap.keySet()) : null;
	}

	public List<String> getTableNameList() {
		checkAndFlush();
		return (tableMap.keySet() != null) ? new ArrayList<String>(tableMap.keySet()) : null;
	}

	public int getAllTableSize() {
		checkAndFlush();
		return allTableList.size();
	}

	public int getTableSize() {
		checkAndFlush();
		return tableMap.size();
	}

	public int getViewSize() {
		checkAndFlush();
		return viewMap.size();
	}

	public Table[] getAllTable() {
		checkAndFlush();
		return allTableList.toArray(new Table[getAllTableSize()]);
	}

	public Table getTable(String tableName) {
		checkAndFlush();
		return tableMap.get(toLowerCase(tableName));
	}

	public Table getView(String tableName) {
		checkAndFlush();
		return viewMap.get(toLowerCase(tableName));
	}

	public List<Column> getColumnList(String tableName) {
		checkAndFlush();
		List<Column> columnList = null;
		Table table = tableMap.get(toLowerCase(tableName));
		if (table != null) {
			columnList = new ArrayList<Column>();
			setTableColumn(table, columnList);
			return columnList;
		}

		table = viewMap.get(toLowerCase(tableName));
		if (table != null) {
			columnList = new ArrayList<Column>();
			setTableColumn(table, columnList);
		}
		return columnList;
	}

	public PrimaryKey getPrimaryKey(String tableName) {
		checkAndFlush();
		PrimaryKey primaryKey = null;
		Table table = tableMap.get(toLowerCase(tableName));
		if (table != null) {
			primaryKey = table.getPrimaryKey();
		}
		return primaryKey;
	}

	@SuppressWarnings("rawtypes")
	public List<ForeignKey> getForeignKey(String tableName) {
		checkAndFlush();
		List<ForeignKey> foreignKeyList = null;
		Table table = tableMap.get(toLowerCase(tableName));
		if (table != null) {
			Iterator iter = table.getForeignKeyIterator();
			if (iter != null) {
				foreignKeyList = new ArrayList<ForeignKey>();
				while (iter.hasNext()) {
					ForeignKey key = (ForeignKey) iter.next();
					foreignKeyList.add(key);
				}
			}
		}
		return foreignKeyList;
	}

	@SuppressWarnings("rawtypes")
	public List<UniqueKey> getUniqueKey(String tableName) {
		checkAndFlush();
		List<UniqueKey> uniqueKey = null;
		Table table = tableMap.get(toLowerCase(tableName));
		if (table != null) {
			Iterator iter = table.getUniqueKeyIterator();
			if (iter != null) {
				uniqueKey = new ArrayList<UniqueKey>();
				while (iter.hasNext()) {
					UniqueKey key = (UniqueKey) iter.next();
					uniqueKey.add(key);
				}
			}
		}
		return uniqueKey;
	}

	public List<Table> matchTables(String tableName) {
		checkAndFlush();
		if (allTableList == null || allTableList.size() < 1 || tableName == null || tableName.length() < 1) {
			return null;
		}
		tableName = toLowerCase(tableName);
		List<Table> newList = new ArrayList<Table>();
		for (Table table : allTableList) {
			if (table.getName().contains(tableName)) {
				newList.add(table);
			}
		}
		return newList;
	}

	public void showAllTable(Writer out) {
		checkAndFlush();
		if (getAllTableSize() > 0) {
			StringBuilder buf = new StringBuilder();
			for (Table table : this.allTableList) {
				buf.append(table.getName()).append("\t").append(convertNull(table.getComment())).append("\r\n");
			}
			writer(out, buf.toString());
		}
	}

	public void showTable(Writer out) {
		showTables(getTableList(), out);
	}

	public void showView(Writer out) {
		showTables(getViewList(), out);
	}

	@SuppressWarnings("unchecked")
	public void showPrimaryKey(PrimaryKey primaryKey, Writer out) {
		if (primaryKey == null || primaryKey.getColumns().size() < 1) {
			return;
		}
		List<Column> list = primaryKey.getColumns();
		showColumn(list, out);
	}

	public List<Table> matchTables(String tableName, MatchTableType mode) {
		checkAndFlush();
		List<Table> result = new ArrayList<Table>();
		for (Table table : allTableList) {
			String name = table.getName();
			if (MatchTableType.START.equals(mode) && name.startsWith(tableName)) {
				result.add(table);
			} else if (MatchTableType.END.equals(mode) && name.endsWith(tableName)) {
				result.add(table);
			} else if (MatchTableType.ANYWHERE.equals(mode) && name.contains(tableName)) {
				result.add(table);
			}
		}
		return result;
	}
	
	public boolean isFlushed() {
		return flushed;
	}
	
	public String getCatalog() {
		return catalog;
	}

	public String getSchema() {
		return schema;
	}

	public void showColumn(String tableName, Writer out) {
		List<Column> list = getColumnList(tableName);
		showColumn(list, out);
	}
	
	private void showColumn(List<Column> list, Writer out) {
		if (list == null || list.size() < 1) {
			return ;
		}
		StringBuilder buf = new StringBuilder();
		for (Column c : list) {
			buf.append(c.getName()).append("\t").append(convertNull(c.getComment()))
				.append("\t").append(JDBCToHibernateTypeHelper.getJDBCTypeName(c.getSqlTypeCode()))
				.append("\t").append(c.getSqlTypeCode())
				.append("\r\n");
		}
		writer(out, buf.toString());
	}
	
	private void showTables(List<Table> list, Writer out) {
		if (list != null && list.size() > 0) {
			StringBuilder buf = new StringBuilder();
			for (Table item : list) {
				buf.append(item.getName()).append("\t").append(convertNull(item.getComment())).append("\r\n");
			}
			writer(out, buf.toString());
		}
	}

	private void writer(Writer out, String text) {
		try {
			out.write(text);
			out.flush();
		} catch (IOException e) {
			logger.error("数据库表列表输出错误！", e);
		}
	}

	@SuppressWarnings("rawtypes")
	private void setTableColumn(Table table, List<Column> columnList) {
		Iterator iter = table.getColumnIterator();
		while (iter.hasNext()) {
			Column column = (Column) iter.next();
			columnList.add(column);
		}
	}

	private String toLowerCase(String name) {
		return (name != null) ? name.toLowerCase() : null;
	}

	private String convertNull(String value) {
		return (value != null) ? value : "";
	}

	private void clear() {
		allTableList.clear();
		tableMap.clear();
		viewMap.clear();
	}
	
	private void checkAndFlush() {
		if (!this.flushed) {
			this.flush();
		}
	}
}
